Flexbox

整理自 MDN web docs 的笔记,同时参考了 Web Bos 上的什么是 Flexbox系列视频。

介绍

Flexbox 是 Flexible Box Module 的缩写。 它是一种布局模型,允许我们方便地控制 html 元素之间的空间分布和对齐 [2]。

Flexbox 一次只能控制一个维度的定位(行或者列)。二维定位的控制需要依靠网格布局 [2]。

给出以下模板:

<body>
    <div class="container">
    <div class="box box-1">1</div>
    <div class="box box-2">2</div>
    <div class="box box-3">3</div>
    <div class="box box-4">4</div>
    <div class="box box-5">5</div>
    <div class="box box-6">6</div>
    <div class="box box-7">7</div>
    <div class="box box-8">8</div>
    <div class="box box-9">9</div>
    <div class="box box-10">10</div>
    </div>
</body>
```    

上面 div 的行为默认遵循正常的 html 文档流,因此从上到下、从左到右进行渲染,并且会占据整个 body 的宽度,因为它们的 `display` 属性默认是 `block`。

![Normal document flow](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/2.png)

## 弹性项目

当为 `.container`div 设置 `display: flex` 时,所有的直接子 div 将成为弹性项目,并且获得新的行为 [2]:

* 由于 `flex-direction` 默认值为 `row`,因此它们会呈一行排列
* 它们将会从左到右排列

![Display flex](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/3.png)

* 项目不会依靠伸展来适应整个宽度(主轴),相反,它们采用收缩的方式

![Items shrink to fit](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/4.gif)

* 项目将会伸展以适应交叉轴(在这个例子中是高度)。如果各个项目的高度不同,它们将会伸展至与最高的那个项目等高。
    
* `flex-basis` 默认值为 `auto`(项目宽度将取决于其自身的内容)
    
* `flex-wrap` 默认值为 `nowrap`(如果容器的宽度不足以囊括所有的项目,则项目不会换行,只会溢出)
    
出于可视化的目的,我们拉伸容器以占据整个高度。

## 弹性容器

`display: flex` 使容器拓展整个可用宽度;与之相对的,`display: inline-flex` 使容器宽度塌陷至与内容宽度相等。

![Display inline flex](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/5.png)

## 弹性方向

一旦声明为弹性容器,我们就可以将元素看作位于两条轴中。一条是由 `flex-direction` 定义的主轴,一条是与前者垂直的交叉轴 [2]。

`flex-direction` 属性有四个值: `row`, `row-reverse`, `column` 和 `column-reverse`.

默认值是 `row`,它将主轴设置为从左到右的水平方向,而交叉轴从上到下与之垂直相交。同理,`column` 将主轴设置为从上到下的垂直方向,而交叉轴则是从左到右。对这两个值添加 `reverse` ,则主轴将反转 180°,而交叉轴保持不变 [1][2]。

可以通过下图观察这些值对应的弹性项目行为:

![Flex direction](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/6.gif)

## 弹性换行

当容器空间不足以容纳全部弹性项目时,利用 `flex-wrap` 属性处理弹性项目 [3]。

`flex-wrap` 的默认值为 `nowrap`,这意味着如果容器不能在保留项目原始宽度的同时将它们排列成一行的话,项目将会收缩以进行适应。如果由于某些原因无法收缩,则项目会溢出容器外 [1][3]。

通过给项目设置 300px 的宽度,`nowrap` 选项输出下面这个结果:

![flex-wrap: nowrap](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/7.png)

其中,每个项目收缩到大约 70px 以适应容器。

当属性值更新为 `wrap` 时,项目的宽度将等于原先的值,300px。当第一行的宽度不足以容纳 300px 时,项目不再溢出容器外,而是会换行 [3]。每一行都应该被视为是一个独立的弹性容器,任何一个容器内的空间分布均不会影响与之相邻的其他容器 [2]。

![flex-wrap: wrap](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/8.png)

但是为什么弹性项目会占据整个屏幕的高度呢?在第一部分,容器高度被设置为 100vh,因此可用空间被这四行平分以适应 300px 的项目。假如我们没有设置 100vh,则容器高度将等于项目内容的高度,如下图所示 [1]:

![Wrap/height unset](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/9.png)

另一个选项是 `wrap-reverse`,它将会反转交叉轴。通过属性 `flex-direction` 设置的从上到下的方向会被 `wrap-reverse` 转化为从下到上 [1]。

![flex-wrap: wrap-reverse](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/10.png)

通过 `flex-direction: column` 反转主轴,容纳不下的元素将会换行至另一列,同时剩余空间会被平分 [1]。

![flex-wrap/column](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/11.png)

 `wrap-reverse` 选项与 `column` 方向搭配使用,则将反转交叉轴的方向为从右到左,产生如下输出:

![flex-wrap/column](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/12.png)

弹性布局是一维布局,虽然在反转换行的时候,项目会从下到上排列(在方向为 row 的情况下),但是依然保持着从左到右的结构。改变的只有交叉轴。

## 弹性流

`flex-direction` 和 `flex-wrap` 可以在一个单属性中进行声明: `flex-flow: [direction] [wrap]` [2]。
```css
.flex-container {
    flex-flow : column wrap;
}
```    

### 项目之间的空隙

回到主轴方向为 row 且进行换行的情况。通过给项目设置 `width: 33.3333%`,容器能够完全被填满。

![item width: 33.33%](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/13.png)

但是当你让子 div 之间有空隙时,它们将不会像预期的那样进行换行:

![Broken gutter](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/14.png)

可以通过使用 CSS 函数 `calc()` 解决这个问题 [1]:
```css
.flex-item {
    width: calc(33.33333% - 40px);
    margin: 20px;
}
```    

![Gutter](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/15.png)

为了消除容器边缘的空间,这里对容器设置负外边距 [3]:

![Gutter no edge](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/16.png)
```css
.flex-container {
    margin: -20px;
}
```    

## 顺序

`order` 属性允许修改项目的呈现顺序。顺序是以组为单位进行分配的。默认情况下所有的弹性项目都设置为 `order:0` ,这意味着所有的项目位于同一组,并且它们会按照原始顺序进行定位。如果有两个或者两个以上的组,那么各组将会相对于它们的整数值进行排序 [4]。

在下面的例子中,有三个`顺序组`, `-1`, `0` 和 `1`,它们按照如下顺序排列。 
```css
.box-3 { order:  1; }
.box-7 { order:  1; }
.box-8 { order: -1; }
```   

![Order](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/17.png)

表面上,这个属性重新分配了项目,但在诸如使用 tab 键对它们进行遍历的交互中则依然保留它们的原始位置。如果项目顺序与可访问性有关的话,这一点是需要考虑的。同理, `flex-direction` 也是这样 [4]。

![Order and Accessibility](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/18.gif)

## 对齐

![Alignment Digest](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/19.png)

在弹性布局中,沿着轴的项目对齐和空间分布可以通过四个属性控制 [5]:

* `justify-content`:将所有项目在主轴上对齐
* `align-items` :将所有项目在交叉轴上对齐
* `align-self`:将单个项目在主轴上对齐
* `align-content`:控制交叉轴上各条线之间的空间

### justify-content

![justify-content digest](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/20.png)

`justify-content` 是一个在主轴上处理项目的容器属性。最常用的 6 个值是: `flex-start`, `flex-end`, `center`, `space-around`, `space-between`, `space-evenly`。其中,默认值为 `flex-start`。

### align-items

![align-items digest](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/21.png)

`align-items` 同样是一个容器属性,它在交叉轴上处理项目的对齐。 默认值是 `stretch` ,其他值是 `flex-start`, `flex-end`, `center` 和 `baseline` [5]。

如果设置了容器高度,则 `stretch` 属性值会使所有的项目伸展至与容器等高;如果没有设置,则所有项目与最高的项目等高 [5]。上面第一张图片中容器高度设置为 `100vh`,第二张图片则没有设置高度。

### align-content

![align-content digest](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/22.png)

`align-content` 是第四个也是最后一个容器属性,它在交叉轴上分配各条线之间的空间。作为最后一个属性,它的初始值为 `stretch` ,并且和 `justify-content` 一样接受以下几个属性值:`flex-start`, `flex-end`, `center`, `space-around`, `space-between`, `space-evenly` [5]。

### align-self

![align-self digest](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/23.png)

`align-items` 属性实际上是通过给容器内的所有项目设置 `align-self` 而生效的。通过单独设置 `align-self`,可以覆盖先前设置的总的属性值。该属性和 `align-items` 拥有相同的可选值,但是还多了一个 ‘auto’ [5]。

`auto` 会重置 `align-self` 的值,使之重新等于通过 `align-items` 给容器全局定义的值 [5]。

## 弹性项目大小

项目的大小和弹性可以通过三个属性控制:`flex-grow`,`flex-shrink` 和 `flex-basis`。这些属性都在主轴上发挥作用 [2]。

* `flex-grow`:如果有额外空间,每个项目应该如何伸展
* `flex-shrink`:如果空间不足,每个项目应该如何收缩
* `flex-basis`:在设置以上两个属性之前项目的大小

### flex-grow

该属性设置的是`弹性增长系数`,这是一个用于处理项目之间相对大小的比率 [7]。

默认值是 0,这意味着如果有剩余空间,就把这个空间放在最后一个项目的后面 [1]。

![flex-grow-default](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/24.png)

在上面的例子中,`direction` 设置为 `row`,每个弹性项目的宽度是 `60px`。由于容器的宽度是 `980px` ,因此有 `680px` 的可用空间,这个空间称为 `正向自由空间` [7]。

通过将 `flex-grow` 设置为 `1`,正向自由空间将会被弹性项目平分。每个项目的宽度都会增加  `136px`,总的宽度是 `196px` [7]。

![flex-grow: 1](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/25.png)

通过给第三个项目设置 `flex-grow: 2` ,它获得的可用正向自由空间是其他项目的两倍,即比起其他项目的 `173px` ,它的总宽度为 `286px` [7]。

下图中,项目的 `flex-grow` 属性设置为自身的内容值。

![flex-grow: variable](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/26.png)

### flex-shrink

当容器没有足够空间来容纳所有项目时,使用 `flex-shrink` 处理项目大小。因此,它通过收缩项目来处理它们的负向自由空间 [7]。

如下图所示,`980px` 的容器存放着 5 个 `300px` 宽度的容器。由于没有足够空间来容纳所需要的 `1500px`,默认的`弹性收缩系数` `1` 会使每个项目收缩至 `196px`。

![flex-shrink-default](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/27.png)

通过给第三个项目设置 `2` 的比率,它会比其它项目小两倍。

![flex-shrink-1](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/28.png)

下图中,每个项目以自身内容值作为弹性收缩比率。

![flex-shrink-variable](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/29.png)

### flex-basis

`flex-basis` 属性会在实际设置可用空间之前检查每个项目应该具有的大小。默认值是 `auto`,项目宽度要么通过 `width` 显式设置,要么等于内容宽度。它同样也接受像素值 [7]。

下面的 gif 展示了一个 `800px` 宽度的容器和 5 个设置了 `flex-basis: 160px` 的弹性项目。这告诉浏览器:理想情况下有足够的空间放置所有的项目,项目的 `160px` 宽度将会得到保留,并且没有正向/负向自由空间。如果没有足够的空间,由于 `flex-shrink` 默认为 `1`,所有的项目会均匀地收缩。如果有剩余的空间,由于 `flex-grow` 默认为 `0`,剩余空间会放置在最后一个项目的后面。

![flex-basis](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/30.gif)

下面的 gif 中,项目 1 设置为 `flex-shrink: 10`,项目 4 设置为 `flex-grow: 10`。对于负向自由空间,项目 1 减小的宽度是其它项目减小宽度的 10 倍;对于正向自由空间,项目 4 增加的宽度是其它项目增加宽度的 10 倍。

![flex-basis](https://raw.githubusercontent.com/Chorer/MyBlog-Pic/master/Myblogs/31.gif)

`flex-basis` 还可以接受值 `content`。此时,无论有没有设置 `width`,自由空间计算都只会基于项目内容去计算宽度。如果你不打算在计算时考虑项目宽度,则将其设置为 `0`。

### flex

`flex` 是 `flex-grow`, `flex-shrink` 和 `flex-basis` 的简写属性 [2]。

它接受下面的预定于值:

* `initial`:重置为弹性布局的默认值,与 flex: 0 1 auto 效果一样
* `auto`:弹性项目可以根据需要伸展/收缩,与 flex: 1 1 auto 效果一样
* `none`:使项目失去弹性,与 flex: 0 0 auto 效果一样
* `flex: 1`:弹性项目可以伸展/收缩,并且 `flex-basis` 设置为 0 ,与 flex: 1 1 0 效果一样

## Autoprefixer

考虑到跨浏览器兼容性,给属性加上所有必要的前缀很重要,这可以确保提供全面的支持 [1]。

手动给每个属性添加前缀是一项非常繁琐的任务,并且还会徒增样式维护的难度。作为替代方法,[Gulp](https://gulpjs.com/) 可以自动化地完成这些任务。

要使用 Gulp,我们需要将其作为依赖项添加到项目中。这是通过 `package.json` 文件完成的,它负责跟踪依赖项及其版本。通过终端创建文件类型 [1]:
```js
    🌹  npm init
```    
在提示下输入信息,点击回车键进行确认。输出文件大概类似下面这样:
```js

{
    "name": "project-name",
    "version": "1.0.0",
    "description": "Project description",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "author": "Author Name",
    "license": "ISC"
}
    

全局安装 gulp :

    🌹  npm install gulp -g

安装 gulp 和 gulp-autoprefixer 作为项目的依赖性:

    🌹  npm install gulp --save-dev
    🌹  npm install gulp-autoprefixer --save-dev

它们会出现在 package.json 文件的 devDependencies 键下。

创建一个 gulpfile.js 文件:

    🌹  touch gulpfile.js

添加下面内容 [9]:

//gulpfile.js

var gulp = require('gulp');
var autoprefixer = require('gulp-autoprefixer');

gulp.task('styles', function() {
    return gulp.src('./styles.css')
        .pipe(autoprefixer({ browsers: ['last 2 versions'], cascade: false }))
        .pipe(gulp.dest('build'));
});

gulpstyles.css 中提取内容并通过 gulp-autoprefixer 进行传递。输出文件保留在 build 文件夹下。

引用

译者注:
弹性项目:Flex Items
弹性容器:Flex Container
弹性方向:Flex Direction
弹性换行:Flex Wrap
弹性流:Flex Flow
弹性项目大小:Flexbox Sizing


关于一级标题翻不翻译的问题拿捏了很久,最后决定翻译。
如果有条件的话推荐阅读英文原文,因为翻译肯定是比不上原汁原味的文章。我觉得这篇博客的亮点在于:

  • 给出了大量图片和示例(其中不少是动图),对于理解很有帮助;
  • 每一个引用的地方都带有注释,给读者追溯文章信息的源头提供了机会(这一点是第一次看到,值得学习);
  • 涉及到了一些细节,比如交叉轴的确定。一些文章说和顺时针、逆时针有关,其实这是错的。从文章中我们也知道了,交叉轴确实由主轴确定,但是它不一定就是由主轴按照一个方向旋转得到的